﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.IO;

namespace HashFoo.MigratorFoo
{
    public class DbConfigurationReflector : IDisposable
    {
        private readonly string _configAssemblyPath;

        public DbConfigurationReflector(string assemblyPath)
        {
            _configAssemblyPath = assemblyPath;
        }

        public IEnumerable<DbConfiguration> GetAllConfigurations()
        {
            AppDomain.CurrentDomain.AssemblyResolve += (o, a) =>
            {
                foreach (var f in Directory.GetFiles(Path.GetDirectoryName(_configAssemblyPath) ?? string.Empty, "*.dll", SearchOption.AllDirectories))
                    try
                    {
                        if (AssemblyName.GetAssemblyName(f).FullName != a.Name) continue;
                        return Assembly.LoadFrom(f);
                    }
                    // ReSharper disable EmptyGeneralCatchClause
                    // don't really care what the exception was -- we're just looking for assemblies to load.
                    catch { }
                    // ReSharper restore EmptyGeneralCatchClause

                return null;
            };

            var assembly = Assembly.LoadFrom(_configAssemblyPath);

            var configurators = (from t in assembly.GetTypes()
                                 let att = (DbConfigurationFixtureAttribute) Attribute.GetCustomAttribute(t, typeof (DbConfigurationFixtureAttribute))
                                 where att != null
                                 select t).ToList();

            var nhConfigs = new List<DbConfiguration>();
            foreach (var configClass in configurators)
            {
                var props = configClass.GetProperties();
                nhConfigs.AddRange(from p in props
                                   where p.PropertyType.Equals(typeof(NHibernate.Cfg.Configuration))
                                   let configAttr = Attribute.GetCustomAttribute(p, typeof(DbConfigurationAttribute)) as DbConfigurationAttribute
                                   let isDefault = configAttr == null ? false : configAttr.IsDefault
                                   let configName = configAttr == null ? p.Name : configAttr.Name
                                   let nhibConfig = (NHibernate.Cfg.Configuration)p.GetValue(null, null)
                                   select new DbConfiguration { NhibConfig = nhibConfig, IsDefault = isDefault, Name = configName });
            }

            return nhConfigs;
        }

        #region AppDomain Magic
        public class DbConfigReflectorWorker : MarshalByRefObject
        {
            private Dictionary<string, Assembly> _assemblyCache = new Dictionary<string, Assembly>();

            public string ConfigAssemblyPath { get; set; }

            public DbConfigReflectorWorker()
            {
                //AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            }

            public IDictionary<string, NHibernate.Cfg.Configuration> GetAllConfigurations()
            {
                var assembly = Assembly.Load(AssemblyName.GetAssemblyName(this.ConfigAssemblyPath));
                var types = assembly.GetTypes();

                var configurators = new List<Type>();
                foreach (var t in types)
                {
                    var att = (DbConfigurationFixtureAttribute)Attribute.GetCustomAttribute(t, typeof(DbConfigurationFixtureAttribute));
                    if (att != null)
                    {
                        configurators.Add(t);
                    }
                }

                var nhConfigs = new Dictionary<string, NHibernate.Cfg.Configuration>();
                foreach (var configClass in configurators)
                {
                    var props = configClass.GetProperties();
                    foreach (var p in props)
                    {
                        if (p.PropertyType.Equals(typeof(NHibernate.Cfg.Configuration)))
                        {
                            var nhibConfig = (NHibernate.Cfg.Configuration)p.GetValue(null, null);
                            nhConfigs.Add(p.Name, nhibConfig);
                        }
                    }
                }

                return nhConfigs;
            }

            private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
            {
                var assemblyName = args.Name;

                if (_assemblyCache.ContainsKey(assemblyName))
                {
                    return _assemblyCache[args.Name];
                }

                var dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
                foreach (var file in Directory.GetFiles(dir, "*.dll"))
                {
                    string fullFile = Path.Combine(dir, file);

                    try
                    {
                        if (AssemblyName.GetAssemblyName(fullFile).FullName == assemblyName)
                        {
                            _assemblyCache.Add(assemblyName, Assembly.LoadFrom(fullFile));
                            return _assemblyCache[assemblyName];
                        }
                    }
                    catch { } // ignore load exceptions
                }

                return null;
            }
        }

        //private void LoadDomain()
        //{
        //    this.UnloadDomain();

        //    var setup = new AppDomainSetup();

        //    //For paralell tests, we need to use distinct application name
        //    setup.ApplicationName = "ReflectorLoader" + "_" + Environment.TickCount;

        //    var appBase = Path.GetDirectoryName(_configAssemblyPath);

        //    // appbase validation?
        //    setup.ApplicationBase = appBase;

        //    // Allow for config file loading?
        //    //setup.ConfigurationFile = Path.Combine(appBase, configFile);

        //    var binPath = appBase;
        //    //if (package.AutoBinPath)
        //    //    binPath = GetPrivateBinPath(appBase, package.Assemblies);
        //    setup.PrivateBinPath = binPath;


        //    //setup.ShadowCopyFiles = "false";
        //    //setup.ShadowCopyDirectories = appBase;
        //    //setup.CachePath = GetCachePath();

        //    var domainName = "migration-reflector-" + Path.GetFileNameWithoutExtension(_configAssemblyPath);

        //    // Setup the Evidence
        //    var evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
        //    if (evidence.Count == 0)
        //    {
        //        var zone = new Zone(System.Security.SecurityZone.MyComputer);
        //        evidence.AddHost(zone);
        //        var assembly = Assembly.GetExecutingAssembly();
        //        var url = new Url(assembly.CodeBase);
        //        evidence.AddHost(url);
        //        var hash = new Hash(assembly);
        //        evidence.AddHost(hash);
        //    }

        //    _workerDomain = AppDomain.CreateDomain(domainName, evidence, setup);

        //    // HACK: Only pass down our AddinRegistry one level so that tests of NUnit
        //    // itself start without any addins defined.
        //}

        //private void UnloadDomain()
        //{
        //    if (_workerDomain != null)
        //    {
        //        AppDomain.Unload(_workerDomain);
        //        _workerDomain = null;
        //    }
        //}
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            //this.UnloadDomain();
        }

        #endregion
    }
}
